package hypeerweb;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.TreeSet;

import network.GlobalObjectId;
import network.LocalObjectId;
import network.NodeProxy;
import network.ObjectDB;
import network.PortNumber;

/**
 * A Node in a HyPeerWeb
 * 
 * @author Brian, Thomas, Jared, Krista, Todd Javadoc by Brian
 */
public class Node implements Comparable<Node>, Serializable {
	private static final long serialVersionUID = 4087742943818820540L;
	/**
	 * The single instance of the Null Node
	 */
	public static final Node NULL_NODE = new Node();

	/**
	 * The WebId of this Node
	 */
	private WebId webId;

	/**
	 * The set of all neighboring Nodes whose WebId's are greater than this
	 * Nodes's
	 */
	private TreeSet<Node> largerNeighbors;

	/**
	 * The set of all neighboring Nodes without children and whose WebId's are
	 * smaller than this Nodes's
	 */
	private TreeSet<Node> smallerNeighborsWithoutChildren;

	/**
	 * The set of all neighboring Nodes with childeren and whose WebId's are
	 * smaller than this Nodes's
	 */
	private TreeSet<Node> smallerNeighborsWithChildren;

	/**
	 * The set of all Nodes that are surrogate neighbors of this Node
	 */
	private TreeSet<Node> downPointers;

	/**
	 * The set of all Nodes that are inverse surrogate neighbors of this Node
	 */
	private TreeSet<Node> upPointers;

	/**
	 * The Node that is the fold of this Node
	 */
	private Node fold;

	/**
	 * The Node that is the surrogate fold of this Node
	 */
	private Node surrogateFold;

	/**
	 * The Node that is the inverse surrogate fold of this Node
	 */
	private Node inverseSurrogateFold;

	/**
	 * The NodeState that this Node is in; Standard, DownPointing, or Cap
	 */
	private NodeState state;

	/**
	 * The Contents of this Node
	 */
	private Contents contents;

	private LocalObjectId localId;

	/**
	 * The default constructor. It is private and should only be used to
	 * construct the NULL_NODE;
	 * 
	 * @pre None
	 * @post webId = NULL_WEB_ID all connections are empty or reference
	 *       NULL_NODE
	 */
	private Node() {
		webId = WebId.NULL_WEB_ID;
		largerNeighbors = new TreeSet<Node>();
		smallerNeighborsWithChildren = new TreeSet<Node>();
		smallerNeighborsWithoutChildren = new TreeSet<Node>();
		upPointers = new TreeSet<Node>();
		downPointers = new TreeSet<Node>();
		fold = this;
		surrogateFold = this;
		inverseSurrogateFold = this;
		state = StandardNodeState.getSingleton();
		contents = new Contents();
		localId = new LocalObjectId();
	}

	/**
	 * The standard Node constructor that creates a new Node from the given id.
	 * 
	 * @param webIdValue
	 *            The value of the new webId.
	 * @pre webId >= 0
	 * @post this.webId.getValue() == webId all connections are empty or
	 *       references to NULL_NODE, except fold this.fold == this
	 */
	public Node(int webIdValue) {
		webId = new WebId(webIdValue);
		largerNeighbors = new TreeSet<Node>();
		smallerNeighborsWithChildren = new TreeSet<Node>();
		smallerNeighborsWithoutChildren = new TreeSet<Node>();
		upPointers = new TreeSet<Node>();
		downPointers = new TreeSet<Node>();
		fold = this;
		surrogateFold = Node.NULL_NODE;
		inverseSurrogateFold = Node.NULL_NODE;
		state = CapNodeState.getSingleton();
		contents = new Contents();
		localId = new LocalObjectId();
	}

	/**
	 * The Node constructor that creates a new Node with the given WebId.
	 * 
	 * @param webId
	 *            The new webId.
	 * @pre webId != NULL_WEB_ID webId != null
	 * @post this.webId == webId all connections are empty or references to
	 *       NULL_NODE, except fold this.fold == this
	 */
	public Node(WebId webId) {
		this.webId = webId;
		largerNeighbors = new TreeSet<Node>();
		smallerNeighborsWithChildren = new TreeSet<Node>();
		smallerNeighborsWithoutChildren = new TreeSet<Node>();
		upPointers = new TreeSet<Node>();
		downPointers = new TreeSet<Node>();
		fold = this;
		surrogateFold = Node.NULL_NODE;
		inverseSurrogateFold = Node.NULL_NODE;
		state = CapNodeState.getSingleton();
		contents = new Contents();
		localId = new LocalObjectId();
	}

	// Accessors

	/**
	 * Accessor for the WebId of this Node
	 * 
	 * @return webId
	 */
	public WebId getWebId() {
		return webId;
	}

	/**
	 * Accessor for all neighbors of this Node
	 * 
	 * @return Union of largerNeighbors, smallerNeighborsWithChildren, and
	 *         smallerNeighborsWithoutChildren
	 */
	public TreeSet<Node> getAllNeighbors() {
		TreeSet<Node> neighbors = new TreeSet<Node>();
		neighbors.addAll(largerNeighbors);
		neighbors.addAll(smallerNeighborsWithChildren);
		neighbors.addAll(smallerNeighborsWithoutChildren);
		return neighbors;
	}

	/**
	 * Accessor for contents of this Node
	 * 
	 * @return contents
	 */
	public Contents getContents() {
		return contents;
	}

	/**
	 * Accessor for all larger neighbors of this Node
	 * 
	 * @return largerNeighbors
	 */
	public TreeSet<Node> getLargerNeighbors() {
		return largerNeighbors;
	}

	/**
	 * Accessor for largest neighbor of this Node
	 * 
	 * @return the largest neighbor of this Node
	 */
	public Node getLargestNeighbor() {
		return !largerNeighbors.isEmpty() ? getLargerNeighbors().last()
				: Node.NULL_NODE;
	}

	/**
	 * Accessor for all smaller neighbors without children of this Node
	 * 
	 * @return smallerNeighborsWithoutChildren;
	 */
	public TreeSet<Node> getSmallerNeighborsWithoutChildren() {
		return smallerNeighborsWithoutChildren;
	}

	/**
	 * Accessor for smallest neighbor of this Node wihtout children
	 * 
	 * @return the smallest neighbor of this Node
	 */
	public Node getSmallestNeighborWithoutChildren() {
		return !smallerNeighborsWithoutChildren.isEmpty() ? smallerNeighborsWithoutChildren
				.first() : Node.NULL_NODE;
	}

	/**
	 * Accessor for all smaller neighbors with children of this Node
	 * 
	 * @return smallerNeighborsWithChildren
	 */
	public TreeSet<Node> getSmallerNeighborsWithChildren() {
		return smallerNeighborsWithChildren;
	}

	/**
	 * Accessor for smallest neighbor of this Node
	 * 
	 * @return the smallest neighbor of this Node
	 */
	public Node getSmallestNeighbor() {
		TreeSet<Node> smallerNeighbors = new TreeSet<Node>();
		if (!smallerNeighborsWithChildren.isEmpty())
			smallerNeighbors.addAll(smallerNeighborsWithChildren);
		if (!smallerNeighborsWithoutChildren.isEmpty())
			smallerNeighbors.addAll(smallerNeighborsWithoutChildren);
		if (smallerNeighbors.isEmpty())
			return Node.NULL_NODE;
		else
			return smallerNeighbors.first();

		/*
		 * return !smallerNeighbors.isEmpty() ? smallerNeighbors.first() :
		 * Node.NULL_NODE;
		 */
	}

	/**
	 * Accessor for all down pointers of this Node
	 * 
	 * @return downPointers
	 */
	public TreeSet<Node> getDownPointers() {
		return downPointers;
	}

	/**
	 * Accessor for all up pointers of this Node
	 * 
	 * @return upPointers
	 */
	public TreeSet<Node> getUpPointers() {
		return upPointers;
	}

	/**
	 * Accessor for fold of this Node
	 * 
	 * @return fold
	 */
	public Node getFold() {
		return fold;
	}

	/**
	 * Accessor for surrogate fold of this Node
	 * 
	 * @return surrogateFold
	 */
	public Node getSurrogateFold() {
		return surrogateFold;
	}

	/**
	 * Accessor for inverse surrogate fold of this Node
	 * 
	 * @return inverseSurrogateFold
	 */
	public Node getInverseSurrogateFold() {
		return inverseSurrogateFold;
	}

	/**
	 * Accessor for the NodeState of this Node
	 * 
	 * @return state
	 */
	public NodeState getState() {
		return state;
	}

	// Mutators

	/**
	 * Mutator for the webId of this Node.
	 * 
	 * @param webId
	 *            The new WebId of this Node
	 * @pre webId != null
	 * @post this.webId == webId
	 */
	public void setWebId(WebId webId) {
		assert webId != null;
		this.webId = webId;
	}

	/**
	 * Adds a neighbor to this Node, and adds this Node as a neighbor of the
	 * other
	 * 
	 * @param node
	 *            The new neighbor of this Node
	 * @pre node != null
	 * @post this.getAllNeighbors() contains node node.getAllNeighbors()
	 *       contains this the 3 neighbor collections of both this and node are
	 *       correct
	 */
	public void addNeighbor(Node node) {
		assert node != null;
		if (node != Node.NULL_NODE) {
			if (node.getWebId().getValue() < webId.getValue()) {
				node.addToLargerNeighbors(this);
			} else {
				Node temp = new Node(this.getWebId().createChildsWebId());
				if (this.getAllNeighbors().contains(temp)) {
					node.addToSmallerNeighborsWithChildren(this);
				} else {
					node.addToSmallerNeighborsWithoutChildren(this);
				}
			}
			
			if (node.getWebId().getValue() > webId.getValue()) {
				largerNeighbors.add(node);
			} else {
				Node temp = new Node(node.getWebId().createChildsWebId());
				if (node.getAllNeighbors().contains(temp)) {
					smallerNeighborsWithChildren.add(node);
				} else {
					smallerNeighborsWithoutChildren.add(node);
				}
			}
		}
	}

	/**
	 * Removes a neighbor from this Node, and removes this Node from the
	 * neighbors of the other
	 * 
	 * @param node
	 *            The neighbor to be removed from this Node
	 * @pre node != null
	 * @post this.getAllNeighbors() doesn't contain node node.getAllNeighbors()
	 *       doesn't contain this The 3 neighbor collections of both this Node
	 *       and node are correct
	 */
	public void removeNeighbor(Node node) {
		assert node != null;
		if (node != null) {
			smallerNeighborsWithChildren.remove(node);
			smallerNeighborsWithoutChildren.remove(node);
			largerNeighbors.remove(node);

			node.removeFromSmallerNeighborsWithChildren(this);
			node.removeFromSmallerNeighborsWithoutChildren(this);
			node.removeFromLargerNeighbors(this);
		}
	}

	/**
	 * Adds a downPointer to this Node, and adds this Node as an upPointer of
	 * the downPointer
	 * 
	 * @param node
	 *            The new downPointer of this Node
	 * @pre node != null
	 * @post this.downPointers contains node node.upPointers contains this
	 */
	public void addDownPointer(Node node) {
		assert node != null;
		if (node != Node.NULL_NODE) {
			downPointers.add(node);
			node.addToUpPointers(this);
			setState(DownPointingNodeState.getSingleton());
		}
	}

	/**
	 * Removes a downPointer from this Node, and removes this Node from the
	 * upPointers of the downPointer
	 * 
	 * @param node
	 *            The downPointer to be removed from this Node
	 * @pre node != null
	 * @post this.downPointers doesn't contain node node.upPointers doesn't
	 *       contain this The state of this Node is correct
	 */
	public void removeDownPointer(Node node) {
		assert node != null;
		if (node != Node.NULL_NODE) {
			downPointers.remove(node);
			node.removeFromUpPointers(this);
			if (getDownPointers().isEmpty()) {
				setState(StandardNodeState.getSingleton());
			}
		}
	}

	/**
	 * Adds an upPointer to this Node, and adds this Node as an downPointer of
	 * the upPointer
	 * 
	 * @param node
	 *            The new upPointer of this Node
	 * @pre node != null
	 * @post this.upPointers contains node node.downPointers contains this
	 */
	public void addUpPointer(Node node) {
		assert node != null;
		if (node != Node.NULL_NODE) {
			upPointers.add(node);
			node.addToDownPointers(this);
			node.setState(DownPointingNodeState.getSingleton());
		}
	}

	/**
	 * Removes an upPointer from this Node, and removes this Node from the
	 * downPointers of the downPointer
	 * 
	 * @param node
	 *            The upPointer to be removed from this Node
	 * @pre node != null
	 * @post this.upPointers doesn't contain node node.downPointers doesn't
	 *       contain this The state of this Node is correct The neighbors of
	 *       this Node's parent are notified, and updated accordingly
	 */
	public void removeUpPointer(Node node) {
		assert node != null;
		if (node != Node.NULL_NODE) {
			upPointers.remove(node);
			node.removeFromDownPointers(this);
			if (node.getDownPointers().isEmpty()) {
				node.setState(StandardNodeState.getSingleton());
				if (node.getSmallerNeighborsWithChildren().size() > 0) {
					Node parent = node.getSmallerNeighborsWithoutChildren().first();
					for (Node neighbor : parent.getLargerNeighbors()) {
						neighbor.removeFromSmallerNeighborsWithChildren(parent);
						neighbor.addToSmallerNeighborsWithoutChildren(parent);
					}
				}
			}
		}
	}

	/**
	 * Mutator for the fold of this Node.
	 * 
	 * @param node
	 *            The new fold of this Node
	 * @pre none
	 * @post this.fold == node and node.fold == this unless node is either null
	 *       or NULL_NODE, in which case this.fold == NULL_NODE
	 */
	public void setFold(Node node) {
		if (node != null && node != Node.NULL_NODE) {
			fold = node;
			node.foldIs(this);
		} else {
			fold = Node.NULL_NODE;
		}
	}

	
	/**
	 * Mutator for the surrogate fold of this Node.
	 * 
	 * @param node
	 *            The new surrogate fold of this Node
	 * @pre none
	 * @post this.surrogateFold == node and node.inverseSurrogateFold == this
	 *       unless node is either null or NULL_NODE, in which case
	 *       this.surrogateFold == NULL_NODE
	 */
	public void setSurrogateFold(Node node) {
		if (node != null && node != Node.NULL_NODE) {
			surrogateFold = node;
			node.inverseSurrogateFoldIs(this);
		} else {
			surrogateFold = Node.NULL_NODE;
		}
	}

	/**
	 * Mutator for the inverse surrogate fold of this Node.
	 * 
	 * @param node
	 *            The new inverse surrogate fold of this Node
	 * @pre none
	 * @post this.inverseSurrogateFold == node and node.surrogateFold == this
	 *       unless node is either null or NULL_NODE, in which case
	 *       this.inverseSurrogateFold == NULL_NODE
	 */
	public void setInverseSurrogateFold(Node node) {
		if (node != null && node != Node.NULL_NODE) {
			inverseSurrogateFold = node;
			node.surrogateFoldIs(this);
		} else {
			inverseSurrogateFold = Node.NULL_NODE;
		}
	}

	/**
	 * Mutator for the state of this Node
	 * 
	 * @param state
	 *            the new state of this node
	 */
	public void setState(NodeState state) {
		assert state != null;
		this.state = state;
	}

	/**
	 * Clears all information connecting this Node to the HyPeerWeb
	 */
	private void clearAllConnections() {
		downPointers.clear();
		upPointers.clear();
		largerNeighbors.clear();
		smallerNeighborsWithChildren.clear();
		smallerNeighborsWithoutChildren.clear();
		fold = Node.NULL_NODE;
		surrogateFold = Node.NULL_NODE;
		inverseSurrogateFold = Node.NULL_NODE;
		state = NodeState.getNodeState(StandardNodeState.STATE_ID);
		webId = WebId.NULL_WEB_ID;
	}

	// Public Operations

	/**
	 * Overrides the default compareTo(Object o). Used for putting Nodes into
	 * TreeSets
	 * 
	 * @param o
	 *            The Node this Node is being compared to
	 * @pre o != null
	 * @post the return value is a correct comparison between this and o
	 */
	@Override
	public int compareTo(Node o) {
		Integer a = this.webId.getValue();
		Integer b = o.getWebId().getValue();
		return a.compareTo(b);
	}

	/**
	 * Creates a simplified representation of this Node
	 * 
	 * @return A SimplifiedNodeDomain that represents this Node
	 * @pre none
	 * @post return value is a correct representation of this Node
	 */
	public SimplifiedNodeDomain constructSimplifiedNodeDomain() {
		HashSet<Integer> neighborIds = getNodeCollectionIds(getAllNeighbors());
		HashSet<Integer> upPointerIds = getNodeCollectionIds(upPointers);
		HashSet<Integer> downPointerIds = getNodeCollectionIds(downPointers);
		SimplifiedNodeDomain temp = new SimplifiedNodeDomain(webId.getValue(),
				webId.getHeight(), neighborIds, upPointerIds, downPointerIds,
				fold.getWebId().getValue(), surrogateFold.getWebId().getValue(),
				inverseSurrogateFold.getWebId().getValue(), state.getStateId());
		return temp;
	}

	/**
	 * Adds newNode to the HyPeerWeb, making all the required updates in order
	 * to keep the HyPeerWeb functional
	 * 
	 * @param newNode
	 *            The Node to be added
	 * @pre The HyPeerWeb is in a valid configuration according to the
	 *      definition of a HyPeerWeb newNode != null
	 * @post newNode is a member of the HyPeerWeb All Nodes previously existing
	 *       in the HyPeerWeb are still there and their WebId's haven't changed
	 *       values The HyPeerWeb is still in a valid state If newNode ==
	 *       NULL_NODE, nothing happens
	 */
	public void addToHyPeerWeb(Node newNode) {
		assert newNode != null;
		if (newNode != NULL_NODE) {
			Node cap = state.findCapNodeFrom(this);
			Node insertionPoint = (new FindInsertionPoint(cap)).execute();
			insertionPoint.addChild(newNode);
		}
	}

	public boolean isProxy() {
		return false;
	}

	/**
	 * removes this node from the HyPeerWeb, making all the required updates in
	 * order to keep the HyPeerWeb functional
	 * 
	 * @pre The HyPeerWeb is in a valid configuration according to the
	 *      definition of a HyPeerWeb AND this node is a member of the HyPeerWeb
	 * @post The HyPeerWeb is still in a valid configuration AND the size of the
	 *       HyPeerWeb is the previous size-1
	 */
	public void removeFromHyPeerWeb() {
		Node cap = state.findCapNodeFrom(this);
		Node deletionPoint = new FindDeletionPoint(cap).execute();
		deletionPoint.replace(this);
	}

	// Private Operations

	/**
	 * Determines which of the 3 neighbor collections a neighbor should be added
	 * to
	 * 
	 * @param neighbor
	 *            The neighbor to be added
	 * @return The correct neighbor collection, based on the configuration of
	 *         the neighbor
	 */
	/**
	 * Converts a TreeSet<Node> into a HashSet<Integer> of their WebId values
	 * 
	 * @param nodes
	 *            The Nodes you want the WebIds of
	 * @return A HashSet of all the WebId values in nodes
	 * @pre nodes != null
	 * @post return value contains all WebId values contained in nodes
	 */
	private HashSet<Integer> getNodeCollectionIds(TreeSet<Node> nodes) {
		HashSet<Integer> ids = new HashSet<Integer>();
		for (Node node : nodes) {
			ids.add(node.getWebId().getValue());
		}
		return ids;
	}

	private HashSet<Integer> getNodeCollectionHashes(TreeSet<Node> nodes) {
		HashSet<Integer> ids = new HashSet<Integer>();
		for (Node node : nodes) {
			ids.add(node.hashCode());
		}
		return ids;
	}

	/**
	 * Adds newNode as a child of this Node
	 * 
	 * @param newNode
	 *            The Node to be added as a child of this Node
	 */
	private void addChild(Node newNode) {
		newNode.setWebId(webId.createChildsWebId());
		updateNeighborsAfterAdd(newNode);
		updateFoldsAfterAdd(newNode);
		updateStatesAfterAdd(newNode);
		webId.incrementHeight();
	}

	/**
	 * Updates all neighbor connections that occur when newNode is added as a
	 * child of this Node
	 * 
	 * @param newNode
	 *            The Node being added
	 * @pre The HyPeerWeb is in a valid configuration based on the definition of
	 *      a HyPeerWeb newNode != null
	 * @post The HyPeerWeb has all correct neighbors as if newNode has been
	 *       inserted in the HyPeerWeb All previously existing Nodes in the
	 *       HyPeerWeb are still there and their WebId's haven't changed value
	 */
	private void updateNeighborsAfterAdd(Node newNode) {
		assert newNode != null;
		for (Node largerNeighbor : largerNeighbors) {
			if (largerNeighbor != newNode) {
				newNode.addDownPointer(largerNeighbor);
				largerNeighbor.removeFromSmallerNeighborsWithoutChildren(this);
				largerNeighbor.addToSmallerNeighborsWithChildren(this);
			}
		}
		newNode.addNeighbor(this);
		while (!upPointers.isEmpty()) {
			Node upPointer = upPointers.first();
			newNode.addNeighbor(upPointer);
			removeUpPointer(upPointer);
		}
	}

	/**
	 * Updates all fold connections that occur when newNode is added as a child
	 * of this Node
	 * 
	 * @param newNode
	 *            The Node being added
	 * @pre updateNeighbors has been called newNode != null insertionPoint !=
	 *      null insertionPoint is the correct Node to insert newNode at
	 * @post The HyPeerWeb has all correct folds as if newNode has been inserted
	 *       in the HyPeerWeb All previously existing Nodes in the HyPeerWeb are
	 *       still there and their WebId's haven't changed value
	 */
	private void updateFoldsAfterAdd(Node newNode) {
		if (inverseSurrogateFold == NULL_NODE) {
			if (fold == this) {
				newNode.setFold(this);
			} else {
				newNode.setFold(fold);
				setSurrogateFold(fold);
				setFold(NULL_NODE);
			}
		} else {
			newNode.setFold(inverseSurrogateFold);
			inverseSurrogateFold.setSurrogateFold(NULL_NODE);
			inverseSurrogateFold = NULL_NODE;
			newNode.setInverseSurrogateFold(NULL_NODE);
		}
	}

	/**
	 * Updates all state that occur when newNode is added as a child of this
	 * Node
	 * 
	 * @param newNode
	 *            The Node being added
	 * @pre updateFolds has been called newNode != null insertionPoint != null
	 *      insertionPoint is the correct Node to insert newNode at
	 * @post The HyPeerWeb is in a valid configuration according to the
	 *       definition of a HyPeerWeb All previously existing Nodes in the
	 *       HyPeerWeb are still there and their WebId's haven't changed value
	 *       newNode is a member of the HyPeerWeb
	 */
	private void updateStatesAfterAdd(Node newNode) {
		switch (getState().getStateId()) {
		case StandardNodeState.STATE_ID:
			newNode.setState(DownPointingNodeState.getSingleton());
			break;
		case CapNodeState.STATE_ID:
			setState(StandardNodeState.getSingleton());
			newNode.setState(CapNodeState.getSingleton());
			for (Node neighbor : largerNeighbors) {
				neighbor.removeFromSmallerNeighborsWithChildren(this);
				neighbor.addToSmallerNeighborsWithoutChildren(this);
			}
			break;
		default:
			throw new IllegalArgumentException();
		}
	}

	/**
	 * Replaces oldNode in all of its connections
	 * 
	 * @param oldNode
	 *            The node we are taking the connections from
	 */
	private void replace(Node oldNode) {
		disconnect();

		setWebId(oldNode.getWebId());
		for (Node downPointer : new TreeSet<Node>(oldNode.getDownPointers())) {
			oldNode.removeFromDownPointers(downPointer);
			downPointer.removeFromUpPointers(oldNode);
			downPointer.addToUpPointers(this);
			this.addToDownPointers(downPointer);
		}

		for (Node upPointer : new TreeSet<Node>(oldNode.getUpPointers())) {
			oldNode.getUpPointers().remove(upPointer);
			upPointer.removeFromDownPointers(oldNode);
			upPointer.addToDownPointers(this);
			this.addToUpPointers(upPointer);
		}

		for (Node neighbor : new TreeSet<Node>(
				oldNode.getSmallerNeighborsWithoutChildren())) {
			neighbor.removeFromLargerNeighbors(oldNode);
			neighbor.addToLargerNeighbors(this);
			smallerNeighborsWithoutChildren.add(neighbor);
		}

		for (Node neighbor : new TreeSet<Node>(
				oldNode.getSmallerNeighborsWithChildren())) {
			neighbor.removeFromLargerNeighbors(oldNode);
			neighbor.addToLargerNeighbors(this);
			smallerNeighborsWithChildren.add(neighbor);
		}

		for (Node neighbor : new TreeSet<Node>(oldNode.getLargerNeighbors())) {
			if (neighbor.removeFromSmallerNeighborsWithChildren(oldNode)) {
				neighbor.addToSmallerNeighborsWithChildren(this);
			}
			if (neighbor.removeFromSmallerNeighborsWithoutChildren(oldNode)) {
				neighbor.addToSmallerNeighborsWithoutChildren(this);
			}
			largerNeighbors.add(neighbor);
		}

		setFold(oldNode != oldNode.getFold() ? oldNode.getFold() : this);
		setSurrogateFold(oldNode.getSurrogateFold());
		setInverseSurrogateFold(oldNode.getInverseSurrogateFold());
		setState(oldNode.getState());
	}

	private void fixToChild(TreeSet<Node> starter) {
		Node larger;
		for (Node node : starter) {
			larger = node.getLargestNeighbor();
			larger.removeFromSmallerNeighborsWithoutChildren(node);
			larger.addToSmallerNeighborsWithChildren(node);
			node.addAllToSmallerNeighborsWithChildren(node.getSmallerNeighborsWithoutChildren());
			node.clearSmallerNeighborsWithoutChildren();
			fixToChild(node.getSmallerNeighborsWithChildren());
		}
	}
	
	

	/**
	 * Disconnects this Node from the HyPeerWeb making all the required updates
	 * in order to keep the HyPeerWeb functional
	 * 
	 * @pre this node is a member of the HyPeerWeb
	 * @post this node is no longer connected to the HyPeerWeb The HyPeerWeb is
	 *       not in a proper configuration after this step
	 */
	private void disconnect() {
		Node smallestNeighbor;

		if (!smallerNeighborsWithChildren.isEmpty()) {
			smallestNeighbor = smallerNeighborsWithChildren.first();
		} else {
			smallestNeighbor = getSmallestNeighborWithoutChildren();
		}
		if (state == CapNodeState.getSingleton()) {
			fixToChild(smallestNeighbor.getSmallerNeighborsWithoutChildren());
		}

		smallestNeighbor.decrementWebIdHeight();
		updateNeighborsAfterDisconnect(smallestNeighbor);
		updateFoldsAfterDisconnect(smallestNeighbor);
		updateStatesAfterDisconnect(smallestNeighbor);
		clearAllConnections();
	}

	
	/**
	 * @param smallestNeighbor
	 * @pre smallestNeighbor != null
	 * @post all neighbors of this do not have this as a neighbor
	 */
	private void updateNeighborsAfterDisconnect(Node smallestNeighbor) {
		removeNeighbor(smallestNeighbor);

		for (Node neighbor : new TreeSet<Node>(smallerNeighborsWithChildren)) {
			removeNeighbor(neighbor);
			neighbor.addDownPointer(smallestNeighbor);
		}

		for (Node neighbor : new TreeSet<Node>(smallerNeighborsWithoutChildren)) {
			removeNeighbor(neighbor);
			neighbor.addDownPointer(smallestNeighbor);
		}

		for (Node neighbor : new TreeSet<Node>(getDownPointers())) {
			removeDownPointer(neighbor);
		}

		for (Node largerNeighbor : smallestNeighbor.getLargerNeighbors()) {
			largerNeighbor.removeFromSmallerNeighborsWithChildren(smallestNeighbor);
			largerNeighbor.addToSmallerNeighborsWithoutChildren(smallestNeighbor);
		}

		smallestNeighbor.addAllToSmallerNeighborsWithChildren(smallestNeighbor.getSmallerNeighborsWithoutChildren());
		smallestNeighbor.clearSmallerNeighborsWithoutChildren();
	}

	/**
	 * @param smallestNeighbor
	 * @pre smallestNeighbor != null
	 * @post all folds of this do not have this as a fold
	 */
	private void updateFoldsAfterDisconnect(Node smallestNeighbor) {
		if (smallestNeighbor != fold) {
			if (smallestNeighbor.getSurrogateFold() != NULL_NODE) {
				smallestNeighbor.setFold(smallestNeighbor.getSurrogateFold());
				smallestNeighbor.getSurrogateFold()
						.setInverseSurrogateFold(NULL_NODE);
				smallestNeighbor.setSurrogateFold(NULL_NODE);
			} else {
				smallestNeighbor.setInverseSurrogateFold(fold);
				fold.setFold(NULL_NODE);
			}
		} else {
			smallestNeighbor.setFold(smallestNeighbor);
			smallestNeighbor.setSurrogateFold(NULL_NODE);
		}
	}

	/**
	 * @param smallestNeighbor
	 * @pre smallestNeighbor != null
	 * @post smallestNeighbor has the correct state
	 */
	private void updateStatesAfterDisconnect(Node smallestNeighbor) {
		if (state.getStateId() == NodeState.CAP_NODE_STATE) {
			smallestNeighbor.setState(CapNodeState.getSingleton());
		}
	}

	public void accept(Visitor visitor, Parameters parameters) {
		visitor.visit(this, parameters);
	}

	public String toString() {

		StringBuffer result = new StringBuffer();
		result.append("\n\n");
		result.append("Node:                   ");
		result.append(webId.getValue());
		result.append("(" + hashCode() + ")");
		result.append("\n");

		result.append("Height:                 ");
		result.append(getWebId().getHeight());
		result.append("\n");

		result.append("Larger Neighbors:              ");
		HashSet<Integer> ids = getNodeCollectionIds(getLargerNeighbors());
		HashSet<Integer> hashes = getNodeCollectionHashes(getLargerNeighbors());
		appendListOf(result, ids, hashes);
		result.append("\n");

		result.append("Smaller Neighbors without Children:              ");
		ids = getNodeCollectionIds(getSmallerNeighborsWithoutChildren());
		hashes = getNodeCollectionHashes(getSmallerNeighborsWithoutChildren());
		appendListOf(result, ids, hashes);
		result.append("\n");

		result.append("Smaller Neighbors with Children:              ");
		ids = getNodeCollectionIds(getSmallerNeighborsWithChildren());
		hashes = getNodeCollectionHashes(getSmallerNeighborsWithChildren());
		appendListOf(result, ids, hashes);
		result.append("\n");

		result.append("UpPointers:             ");
		ids = getNodeCollectionIds(getUpPointers());
		hashes = getNodeCollectionHashes(getUpPointers());
		appendListOf(result, ids, hashes);
		result.append("\n");

		result.append("DownPointers:           ");
		ids = getNodeCollectionIds(getDownPointers());
		hashes = getNodeCollectionHashes(getDownPointers());
		appendListOf(result, ids, hashes);
		result.append("\n");

		result.append("Fold:                   ");
		result.append(fold.getWebId().getValue() + " ");
		result.append("(" + fold.hashCode() + ")");
		result.append("\n");

		result.append("Surrogate Fold:         ");
		result.append(surrogateFold.getWebId().getValue() + " ");
		result.append("(" + surrogateFold.hashCode() + ")");
		result.append("\n");

		result.append("Inverse Surrogate Fold: ");
		result.append(inverseSurrogateFold.getWebId().getValue() + " ");
		result.append("(" + inverseSurrogateFold.hashCode() + ")");
		result.append("\n");

		result.append("State: ");
		result.append(NodeState.getNodeState(state.getStateId()).toString());

		return result.toString();
	}

	// public String toString() {
	// return "Node: " + webId.getValue() + "  " + localId.getId();
	// }

	protected void appendListOf(StringBuffer stringBuffer,
			HashSet<Integer> ids, HashSet<Integer> hashes) {
		int[] idList = new int[ids.size()];
		int i = 0;
		for (int element : ids) {
			idList[i] = element;
			i++;
		}

		int[] hashList = new int[hashes.size()];
		i = 0;
		for (int element : hashes) {
			hashList[i] = element;
			i++;
		}

		for (int index = 0; index < idList.length; index++) {
			stringBuffer.append(idList[index] + " ");
			stringBuffer.append("(" + hashList[index] + ")");
			if (index < idList.length - 1) {
				stringBuffer.append(" ");
			}
		}
	}

	public LocalObjectId getLocalObjectId() {
		return localId;
	}
	
	public Object writeReplace() {
		try {
			ObjectDB.getSingleton().store(getLocalObjectId(), this);
			return new NodeProxy(new GlobalObjectId(InetAddress.getLocalHost().getHostAddress(), PortNumber.getApplicationsPortNumber(), getLocalObjectId()));
		} catch (UnknownHostException e) {
			e.printStackTrace();
			return null;
		}
	}


	private void decrementWebIdHeight(){
		webId.decrementHeight();
	}

	private void clearSmallerNeighborsWithoutChildren(){
		smallerNeighborsWithoutChildren.clear();
	}
	private void addAllToSmallerNeighborsWithChildren(TreeSet<Node> set){
		smallerNeighborsWithChildren.addAll(set);
	}
	private void foldIs(Node node){
		fold=node;
	}
	
	private void addToSmallerNeighborsWithChildren(Node node){
		smallerNeighborsWithChildren.add(node);
	}
	
	private void addToSmallerNeighborsWithoutChildren(Node node){
		smallerNeighborsWithoutChildren.add(node);
	}
	
	private void addToLargerNeighbors(Node node){
		largerNeighbors.add(node);
	}
	
	private boolean removeFromSmallerNeighborsWithChildren(Node node){
		return smallerNeighborsWithChildren.remove(node);
	}
	
	private boolean removeFromSmallerNeighborsWithoutChildren(Node node){
		return smallerNeighborsWithoutChildren.remove(node);
	}
	
	private boolean removeFromLargerNeighbors(Node node){
		return largerNeighbors.remove(node);
	}
	
	private void addToUpPointers(Node node){
		upPointers.add(node);
	}
	
	private void addToDownPointers(Node node){
		downPointers.add(node);
	}
	
	private boolean removeFromUpPointers(Node node){
		return upPointers.remove(node);
	}
	
	private boolean removeFromDownPointers(Node node){
		return downPointers.remove(node);
	}

	private void inverseSurrogateFoldIs(Node node){
		inverseSurrogateFold=node;
	}
	private void surrogateFoldIs(Node node){
		surrogateFold=node;
	}
	
	
	
	
	
}
